Welcome back. You've already seen that a function is a useful way to
abstract from a bit of code to something that has a name and has parameters.
The parameters help to make the code more reusable.
You pass in a different parameter value and get a different result and the name
makes it so that you can refer to the whole action of the function with a single name.
It makes your code easier to read and understand.
In this video, we're going to carry the abstraction process one step further.
We can decompose what a function does into a set of actions,
and some of those actions may be implemented in other functions,
and we'll just invoke those other named functions.
So in other words, inside of a functions code,
we're going to invoke another function.
For example, here we have a function sum_of_squares that takes three inputs x, y, and z.
It takes the square of each one on lines 6, 7,
and 8, and it adds them all up and returns the sum.
When in order to get the square of each of those inputs,
we invoke the square function which is defined up here.
So, we're inside the function sum_of _squares,
we're invoking the function square.
Let's see that execute in slow motion.
So, we first we define the two functions,
and then we set values for a,
b, and c, those all get set in
the global frame and now we're ready to invoke sum_of_squares.
So, we get a new stack frame and we get the values for the parameter names.
So, x gets whatever a used to be bound to or still is bound to,
so that minus five came from there.
Y gets the value that b had, which was two.
Z gets the value that c had, which is 10.
Now, when we execute sum_of_squares,
we get to line six,
and on line six, we are invoking the square function.
We're invoking that inside of the definition of sum_of _squares.
So, when we invoke it, we get another stack frame.
So, this is going to give us a local namespace for variables for the execution of square.
It's also interesting to note that we have a variable x,
whose value is minus five.
In sum of the squares,
there's also an x,
whose value is minus five,
but these are different variables.
So, we could change the value of
x in square and that would not have an effect on sum of the squares.
In fact, we will see that because we do a similar thing with the variable y.
On line two we create a value for y in the square stack frame.
So now y has the value 25.
There's also a value for y in the sum_of_squares frame that it has a different value two,
and we have not changed anything here.
Once we continue the execution,
we'll go forward the square stack frame is going to disappear.
We're going to return the value 25,
but the y in sum _of_squares is completely unaffected.
That explains the mechanics.
Now, let's try doing functional decomposition to write a little bit more useful function.
Remember, from last week Wednesday,
we've taught you about dictionaries.
You had some code to accumulate a dictionary of counts.
I've turned that into a function count_freqs,
short for count frequencies.
It takes one formal parameter st short for
string to remind me that the input parameter is going to be a string,
and it's going to count frequencies for whatever string is passed in.
Now, the code should look a little bit familiar.
From last week, we start by making an empty dictionary,
we iterate through all of the characters in the string.
If the character is not yet a key in the dictionary,
meaning this is our first time seeing it,
we add it as a key in the dictionary and we give it an initial value of zero.
Then regardless of whether we've seen this character before or not,
we're going to increment the counter that's associated with it.
So, if this was the first time we saw it,
it starts with a zero and now it's got one for its count.
But if we've already seen this character say three times before,
the count will get updated to be four.
So, the new things here over what you saw last week or that we've turned
the bit of code into a function
and before you just saw how to do this with a particular string.
Now, we're making it work for any string we've
made the string b a formal parameter of the function.
The other thing that's different is instead of printing
out the result at the end or assigning it to a variable,
we just return it and then whatever code is
invoking count_freqs will decide what to do with the return value.
Similarly, you had code last week,
given a dictionary to find the best key in the dictionary,
to find the key that had the maximum value associated with it.
So again, I've turned that into a function,
I called it best_key,
and it will take any dictionary as an input.
That dictionary should have keys and values where the values are
numbers and it's going to return the key that has the highest value.
Given both of these functions count_freqs and best_key,
I can now compose a bigger function most_common_letter.
So, it's going to take a string as an input,
and just to distinguish it I've chosen to give it a different parameter name,
I could have called it st just like I used for
count_freqs but I decided to call it s here.
We're doing this in two steps.
In step one, we count the frequencies in s,
which creates a dictionary,
and I've assigned that to a variable called frequencies.
I pass that dictionary into the best_key function and what I get back is one key,
the key that has the highest value I return that and that should be
the most common letter in our string s. So,
let's run this and make sure that it actually works.
Down at the bottom, I'm invoking most_common_letter on line 21,
and I'm passing in a string that has a bunch of b's bunch of c's.
As you can see that it's got more b's than any of the other letters,
and what we should get as our output is just the letter
b and sure enough that's what we get.
If I change my string and give it a whole lot more c's,
now when I run it I should get the letter c instead,
and sure enough I do.
Now, I want to described this as a composition process.
Often though, you'll actually solve problems in the opposite order by decomposition.
You might start by saying, "Hey,
I want to find the most_common_letter."
So, let's decompose that into first finding
the frequencies of all the letters and then picking the one with the highest frequency.
When I start that decomposition process,
those functions may not exist yet but I just refer
to them by name and then afterwards I find definitions for them.
So really, how I wrote this code,
I started by defining most_common_letter,
and I referred to the functions count_freqs
and best_key even though those functions didn't exist.
Because I had given them names that made me know what they would do,
I was able to write that function and have it be clear what it was going to
do and then I had to go and fill in the other two functions.
As a little aside,
you may be wondering why it works to have on line
two a reference to the count_freqs function which has not been defined yet,
when we talk about executing Python code from top to bottom.
So, we shouldn't be able to refer on line
two to a function that isn't defined until line five.
The reason we don't get an undefined variable error,
is that even though on line two we're referring to count_freqs,
we don't actually execute line two,
until after we invoke on line 21 the most_common_letter function.
So, by the time we actually execute line two count_freqs has been defined.
If we were not inside a function definition,
we really would have a problem.
For example, if I say print of x and then x equals four,
I will get an error because on line one x is not defined.
The difference is that, inside a function,
we're not actually going to refer on line five to count_freqs until line
five executes and by that time the count_freqs function has been defined.
So to summarize, functions can call other functions, it's called composition.
You get multiple stack frames when that
executes each one has its own namespace and its own local variables.
As a problem-solving strategy,
it's helpful to decompose,
to find a function by referring to other functions that don't yet
exist and then write those functions. See you next time.